﻿using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine.Engines.AO {

    public class AmbientOcclusionPath : PathSamplerBase {

        public static RgbSpectrum Add = new RgbSpectrum(0.005f);

        private OcclusionRayInfo[] secondaryRays;
        private int tracedRaysCount, depth;
        private bool SecondaryRaysMode = false;
        SurfaceIntersectionData hitInfo = null;

        struct OcclusionRayInfo {
            public int RayIndex;
            public RayData Ray;
        }

        public override void InitPath(IPathProcessor buffer) {
            base.InitPath(buffer);
            this.Radiance = new RgbSpectrum(0f);
            this.secondaryRays = new OcclusionRayInfo[pathIntegrator.Scene.ShadowRayCount];
            this.Sample = pathIntegrator.Sampler.GetSample(null);
            IRay ray;
            pathIntegrator.Scene.Camera.GetRay(Sample.imageX, Sample.imageY, out ray);
            this.PathRay = (RayData)ray;

            this.RayIndex = -1;
            this.tracedRaysCount = 0;
            this.depth = 0;
            this.SecondaryRaysMode = false;
        }

        public override bool FillRayBuffer(RayBuffer rayBuffer) {
            var leftSpace = rayBuffer.LeftSpace();
            if (((!SecondaryRaysMode) && (1 > leftSpace)) ||((SecondaryRaysMode)  && (tracedRaysCount > leftSpace)))
                return false;
            if (!SecondaryRaysMode)
                RayIndex = rayBuffer.AddRay(ref PathRay);
            if (SecondaryRaysMode) {
                for (int i = 0; i < tracedRaysCount; ++i)
                    secondaryRays[i].RayIndex = rayBuffer.AddRay(ref secondaryRays[i].Ray);
            }
            return true;
        }



        public override void Advance(RayBuffer rayBuffer, SampleBuffer consumer) {
            base.Advance(rayBuffer, consumer);

            if (SecondaryRaysMode && tracedRaysCount > 0) {
                foreach (var occlusionRayInfo in secondaryRays) {
                    Radiance += !rayBuffer.rayHits[occlusionRayInfo.RayIndex].Miss() ? Add : RgbSpectrum.ZeroSpectrum();
                }
                Splat(consumer);
                return;
            }

            var rayHit = rayBuffer.rayHits[RayIndex];
            depth++;

            if (rayHit.Miss()) {
                /*if (depth < 2) {
                    
                    //Radiance += this.scene.SampleEnvironment(PathRay.Dir) * Throughput;
                }*/
                Splat(consumer);
                return;
            }
            
            var hitPoint = PathRay.Point(rayHit.Distance);
            if (hitInfo == null)
            {
                hitInfo = SurfaceSampler.GetIntersection(ref PathRay, ref rayHit);
            }
            else
            {
                SurfaceSampler.GetIntersection(ref PathRay, ref rayHit, ref hitInfo);
            }
            var n = hitInfo.Normal;

            Vector v1, v2;
            Vector.CoordinateSystem(ref n, out v1, out v2);

            Radiance = Add*Vector.AbsDot(ref n, ref PathRay.Dir);
            
            for (int index  = 0; index < secondaryRays.Length; index++) {
                var dir = MC.CosineSampleHemisphere(Sample.GetLazyValue(), Sample.GetLazyValue());
                dir = new Vector( v1.x * dir.x + v2.x * dir.y + n.x * dir.z, v1.y * dir.x + v2.y * dir.y + n.y * dir.z, v1.z * dir.x + v2.z * dir.y + n.z * dir.z);
                secondaryRays[index].Ray = new RayData(hitPoint, dir, 1e-4f, 10f) ;
                tracedRaysCount++;
            }
            SecondaryRaysMode = true;
        }

    }
}
